<template>
  <el-drawer
    model-value="visibilityBinding"
    title="视频上传"
    @close="handleClose"
    :show-close="false"
  >
    <el-upload
      drag
      action
      :auto-upload="false"
      :on-change="handleChange"
      :before-upload="beforeVideoUpload"
      :show-file-list="false"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">
        Drop file here or <em>click to upload</em>
      </div>
      <template #tip>
        <div class="el-upload__tip" style="text-align: center">
          大文件上传和断点续传
        </div>
      </template>
    </el-upload>

    <!-- 进度显示 -->
    <div class="progress-box">
      <el-button
        type="primary"
        size="default"
        @click="handleClickBtn"
        :disabled="finished"
        class="pro-btn"
      >
        {{ btnStatus ? '继续' : '暂停' }}
      </el-button>
      <el-progress
        class="dash-style"
        type="dashboard"
        :percentage="Number(percent.toFixed())"
      >
        <template #default="{ percentage }">
          <span class="percentage-value">{{ percentage }}%</span>
        </template>
      </el-progress>
    </div>

    <div style="margin-bottom: 25px; color: #72767b">视频预览</div>

    <!-- 显示视频 -->
    <div>
      <video-player :src="videoUrl" :options="playerOptions" controls>
      </video-player>
    </div>
  </el-drawer>
</template>

<script setup>
import { ref, defineEmits } from 'vue'
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { ElMessage } from 'element-plus'
import cloneDeep from 'lodash/cloneDeep'

const finished = ref(false)
const emits = defineEmits(['update:modelValue', 'initGetVideos'])
const videoUrl = ref('')

// 按钮状态
const btnStatus = ref(true)
// 进度状态
const percent = ref(0)
// 文件hash值
const fileHash = ref('')
// 块数组
const chunkAllList = ref([])
// 每一个块占总100%多少,作用就是每完成传递一块传输，进度就会在原有的加上这个固定的值直到100%结束
const percentCount = ref(0)
// 切片文件总数量
const chunklength = ref(0)
// 上传文件类型 后端合并时候用
const fileSuffix = ref('')
// 上传文件名称
const fileName = ref('')

// 指定播放器信息
const playerOptions = ref({
  //播放速度
  playbackRates: [0.5, 1.0, 1.5, 2.0],
  //如果true,浏览器准备好时开始回放。
  autoplay: false,
  // 默认情况下将会消除任何音频。
  muted: false,
  // 导致视频一结束就重新开始。
  loop: false,
  // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频（如果浏览器支持）
  preload: 'auto',
  language: 'zh-CN',
  // 将播放器置于流畅模式，并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字（例如"16:9"或"4:3"）
  aspectRatio: '16:9',
  // 当true时，Video.js player将拥有流体大小。换句话说，它将按比例缩放以适应其容器。
  fluid: true,
  sources: [
    {
      //类型
      type: 'video/mp4',
      //url地址 本地方式
      // src: require('@/assets/video/第二章 进程管理1.mp4')
      src: videoUrl.value
    }
  ],
  //你的封面地址
  poster: require('@/assets/background.jpg'),
  //允许覆盖Video.js无法播放媒体源时显示的默认信息。
  notSupportedMessage: '等待下载中。。。'
})
// 用来将File类型转为ArrayBuffer类型
const fileToBuffer = (file) => {
  // 因为是异步的所以需要使用Promise封装
  return new Promise((resolve, reject) => {
    const fr = new FileReader()
    // 读取文件
    fr.readAsArrayBuffer(file)
    // 获取ArrayBuffer对象
    fr.onload = (e) => resolve(e.target.result)
    fr.onerror = () => reject(new Error('类型转换错误'))
  })
}

// 发送请求
const sendRequest = () => {
  const requestList = []
  chunkAllList.value.forEach((item, index) => {
    // 这里封装了每一个块都单独发送一个请求，并将这些块请求放到一个请求数组中
    const fn = () => {
      const formData = new FormData()
      formData.append('chunk', item.chunk)
      formData.append('fileName', item.fileName)
      return axios({
        url: '/api/fmms/videoUploadController/videoUpload',
        method: 'post',
        data: formData,
        headers: {
          'Content-Type': 'mutipart/form-data',
          token: localStorage.getItem('token')
        }
      }).then((res) => {
        if (res.data.code === 200) {
          if (percentCount.value === 0) {
            percentCount.value = 100 / chunklength.value
          }
          percent.value += percentCount.value
          if (percent.value >= 100) {
            percent.value = 100
          }
          // 传递成功后需要移除当前块
          chunkAllList.value.splice(index, 1)
        }
      })
    }
    requestList.push(fn)
  })

  // console.log(requestList.length) // 25

  // 文件全部上传完毕后 需要调用'/merge'接口同时传递文件hash值给服务器进行文件的合并 并获取文件url
  const complete = () => {
    axios({
      url: '/api/fmms/videoUploadController/mergeChunk',
      method: 'get',
      params: {
        hash: fileHash.value,
        fileSuffix: fileSuffix.value,
        fileName: fileName.value
      },
      headers: { token: localStorage.getItem('token') }
    }).then((res) => {
      res = res.data
      if (res.code === 200) {
        emits('initGetVideos')
        finished.value = true
        // 此处处理返回的视频资源
        videoUrl.value = res.data.videoPath
      }
    })
  }

  //记录请求的个数
  let count = 0
  const send = async () => {
    //验证按钮状态
    if (!btnStatus.value) return false
    if (count >= requestList.length) {
      complete()
      return false
    }
    // 没有给块单独发送请求
    await requestList[count]()
    count++
    send()
  }
  send()
}

// 当按钮状态为false 此时需要停止发送请求块
const handleClickBtn = () => {
  btnStatus.value = !btnStatus.value
  if (btnStatus.value) {
    sendRequest()
  }
}

// 获取文件对象
const handleChange = async (uploadFile, uploadFiles) => {
  if (!btnStatus.value) {
    return false
  }
  // 获取文件对象 raw: File 通过点row才能获得File类型对象
  const file = uploadFile.raw
  if (file.type !== 'video/mp4') {
    ElMessage({
      type: 'error',
      duration: 1500,
      message: '仅支持上传mp4格式的视频文件'
    })
    return false
  }

  if (file != null) {
    percent.value = 0
    finished.value = false
  }
  fileName.value = file.name
  // console.log(file)
  // 将文件对象转为ArrayBuffer类型需要使用FileReader类，同时需要使用Promise封装因为是异步的
  let buffer
  try {
    buffer = await fileToBuffer(file)
    // console.log(buffer)
  } catch (error) {
    ElMessage.error(error)
  }

  //进行文件切片，切片大小5M
  // 定义块的大小
  const chunkSize = 5 * 1024 * 1024
  // 定义块存储的数组
  const chunkList = []
  // 计算总共有多少个切片
  const chunkListlength = Math.ceil(file.size / chunkSize)
  chunklength.value = chunkListlength
  // console.log(chunkListlength) //到这里应该是5个切片
  //返回数组取其第一个元素得到文件的后缀 ['.exe', index: 22, input: 'CAJViewer 7.3.151.self.exe', groups: undefined]
  const suffix = /\.([0-9A-z]+)$/.exec(file.name)[0]
  fileSuffix.value = suffix
  // console.log(suffix)
  // 根据文件内容生成hash值
  const spark = new SparkMD5.ArrayBuffer()
  spark.append(buffer)
  // 91684dd14eab7dad400410533bbe6895
  let hash = spark.end()
  // console.log(hash)
  // 生成切片，后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
  // 应为File继承了Blob对象而Blob中提供了slice方法用于数据切片，所以可以可以直接使用File对象进行切片
  // 注意前端这种切片和后端不一样不需要额外减1
  for (let i = 0; i < chunkListlength; i++) {
    const item = {
      /* 使用blob切片时start包含 end不包含 最后一个切片需要注意以下
      slice() 方法第二个参数的默认值将会传入原始 Blob 的长度。 
      如果你在调用的时候传入的 start + length 超出了原始 Blob 的长度，
      那么返回的 Blob  对象将会包含从 start 到原始数据的末尾，因此无需对最后一个切片额外处理。
      */
      chunk: file.slice(i * chunkSize, (i + 1) * chunkSize),
      fileName: `${hash}-${i}${suffix}`
    }
    chunkList.push(item)
  }

  // 以下发送请求时候需要用到
  chunkAllList.value = cloneDeep(chunkList)
  fileHash.value = hash
  // 发送请求上传块数据
  sendRequest()
}

const handleClose = () => {
  emits('update:modelValue', false)
}

const beforeVideoUpload = (rawFile) => {}
</script>

<style lang="scss" scoped>
.el-drawer {
  :global(.el-drawer__header) {
    margin-bottom: 10px !important;
  }
  .progress-box {
    display: flex;
    height: 50px;
    margin-bottom: 20px;
    justify-content: flex-start;
    align-items: center;
    .pro-btn {
      margin-right: 10px;
    }
    & span {
      margin-left: 10px;
    }
    .dash-style {
      :deep(.el-progress-circle) {
        width: 50px !important;
        height: 50px !important;
      }
    }
    .percentage-value {
      display: block;
      margin-top: 3px;
      margin-left: 0px;
      font-size: 17px;
    }
  }
}
</style>
